﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Gunningine
{
    using OpenTK;
    using OpenTK.Graphics;
    using OpenTK.Graphics.OpenGL;
    using System.Drawing;

    internal class Window : GameWindow
    {
        private GameStateManager gameStateMgr;

        private InputManager inputMgr;

        private RenderSystem renderSystem;

        bool fullScreen;

        bool exiting = false;

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="title">title of the window.</param>
        /// <param name="width">width of window in pixels.</param>
        /// <param name="height">height of window in pixels.</param>
        /// <param name="fullScreen">whether to show the window fullscreen or not.</param>
        /// <param name="vSync">whether to sync refresh to v-sync.</param>
        /// <param name="renderSystem">Render system.</param>
        /// <param name="gameStateMgr">GameState manager</param>
        /// <param name="inputMgr">Input manager.</param>
        public Window(string title, int width, int height, bool fullScreen, bool vSync, RenderSystem renderSystem, 
                      GameStateManager gameStateMgr, InputManager inputMgr)
            : base(width, height, GraphicsMode.Default, title)
        {
            this.VSync = vSync ? VSyncMode.On : VSyncMode.Off;
            this.renderSystem = renderSystem;
            this.gameStateMgr = gameStateMgr;
            this.inputMgr = inputMgr;
            this.fullScreen = fullScreen;

            // Set input callbacks
            this.Keyboard.KeyDown += this.inputMgr.HandleKeyPressed;
            this.Keyboard.KeyUp += this.inputMgr.HandleKeyReleased;
            this.Mouse.ButtonDown += this.inputMgr.HandleButtonDown;
            this.Mouse.ButtonUp += this.inputMgr.HandleButtonUp;
            this.Mouse.Move += this.inputMgr.HandleMouseMove;

            // Set the mouse to start in the middle of the window.
            Cursor.Position = new Point(this.Location.X + this.ClientRectangle.Width / 2,
                                        this.Location.Y + this.ClientRectangle.Height / 2);
            this.inputMgr.TeleportMouse(this.Mouse.X, this.Mouse.Y);
            this.CursorVisible = false;
        }

        /// <summary>
        /// Called when the window is created.
        /// </summary>
        /// <param name="e">event arguments.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            Logger.Info("Initialising window...");

            if (this.fullScreen)
            {
                WindowBorder = WindowBorder.Hidden;
                WindowState = WindowState.Fullscreen;
            }

            this.renderSystem.Initialise(this.ClientRectangle.X, 
                                            this.ClientRectangle.Y, 
                                            this.ClientRectangle.Width, 
                                            this.ClientRectangle.Height);

            // Start state management
            this.gameStateMgr.Join();
        }

        /// <summary>
        /// Called when the window is resized.
        /// </summary>
        /// <param name="e">event arguments.</param>
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);

            this.renderSystem.SetDimensions(this.ClientRectangle.X,
                                            this.ClientRectangle.Y,
                                            this.ClientRectangle.Width,
                                            this.ClientRectangle.Height);
                
            this.renderSystem.Set3dProjection(90.0f);
        }

        /// <summary>
        /// Called before next frame update.
        /// </summary>
        /// <param name="e">event arguments.</param>
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);

            if (!this.exiting)
            {
                // Update current state.
                try
                {
                    this.gameStateMgr.Update((float)e.Time, this.inputMgr);
                    this.inputMgr.PostUpdate();
                }
                catch (ExitException)
                {
                    this.exiting = true;
                    Exit();
                }
            }
        }

        /// <summary>
        /// Render a frame.
        /// </summary>
        /// <param name="e">event arguments</param>
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);

            if (!this.exiting)
            {
                // Clear to black
                this.renderSystem.ClearScreen();

                // Render current state
                this.gameStateMgr.Render(this.renderSystem);
            }

            // Blit to screen
            SwapBuffers();
        }
    }
}